<?php


/**
* CakePHP Routines Plugin
* http://cake-routines-plugin.googlecode.com
*
* @package routines
* @subpackage routines.models.behaviors
* @author Ivan Chistyakov <zetruger@gmail.com>
* @version $Id: sortable.php 166 2010-10-22 06:51:50Z zetruger@gmail.com $
* @link $HeadURL: http://cake-routines-plugin.googlecode.com/svn/trunk/models/behaviors/sortable.php $
* @license MIT License (http://opensource.org/licenses/mit-license.php)
*/


class SortableBehavior extends ModelBehavior {

var $_defaults = array(
	'sortField' => 'position',
	'newPositionLast' => FALSE,
	'findCallbacks' => TRUE,
	'neighborhoodConditions' => array()
);

var $settings = array();

function setup(&$Model, $settings = array()) {
	$this->settings[$Model->alias] = $settings;
}

function _getSettings(&$Model, $settings = array()) {
	$settings = Set::merge($this->settings[$Model->alias], $settings);
	if (isset($settings['noUseModelVars']) && $settings['noUseModelVars']) {
		return Set::merge($this->_defaults, $settings);
	} else {
		return Set::merge($this->_defaults, array_intersect_key(get_object_vars($Model), $this->_defaults), $settings);
	}
}

function _idExists(&$Model, $id) {
	$Model->id = $id;
	return $Model->exists();
}

function _conditions(&$Model, $id) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($Model->Behaviors->attached('Neighborable')) {
		return $Model->getNeighborhoodConditions($id);
	} else {
		return $neighborhoodConditions;
	}
}

function positionVerify(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$fieldSchema = $Model->schema($sortField);
	if (isset($fieldSchema['null']) && $fieldSchema['null'] && ($Model->find('count', array('conditions' => Set::merge($conditions, array($Model->escapeField($sortField) => NULL)), 'recursive' => -1, 'callbacks' => $findCallbacks)) > 0)) {
		return FALSE;
	}
	return $Model->find('count', array('conditions' => $conditions, 'recursive' => -1, 'callbacks' => $findCallbacks)) == $Model->find('count', array('conditions' => $conditions, 'fields' => 'DISTINCT ' . $Model->escapeField($sortField), 'recursive' => -1, 'callbacks' => $findCallbacks));
}

function positionReset(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$all = $Model->find('all', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'order' => array($Model->escapeField($Model->primaryKey) => 'ASC'), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (is_array($all)) {
		$numRows = count($all);
		if ($numRows > 0) {
			foreach ($all as $i => $row) {
				$all[$i][$Model->alias][$sortField] = 100 + $i * 10;
			}
			return $Model->saveAll($all, array('validate' => FALSE, 'callbacks' => FALSE, 'atomic' => TRUE));
		}
	}
	return FALSE;
}

function positionResetAll(&$Model, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField)) {
			return FALSE;
		}
	}
	$all = $Model->find('all', array('fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'order' => array($Model->escapeField($Model->primaryKey) => 'ASC'), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (is_array($all)) {
		$numRows = count($all);
		if ($numRows > 0) {
			foreach ($all as $i => $row) {
				$all[$i][$Model->alias][$sortField] = 100 + $i * 10;
			}
			return $Model->saveAll($all, array('validate' => FALSE, 'callbacks' => FALSE, 'atomic' => TRUE));
		}
	}
	return FALSE;
}

function positionRecover(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$all = $Model->find('all', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'order' => array($Model->escapeField($sortField) => 'ASC', $Model->escapeField($Model->primaryKey) => 'ASC'), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (is_array($all)) {
		$numRows = count($all);
		if ($numRows > 0) {
			foreach ($all as $i => $row) {
				$all[$i][$Model->alias][$sortField] = 100 + $i * 10;
			}
			return $Model->saveAll($all, array('validate' => FALSE, 'callbacks' => FALSE, 'atomic' => TRUE));
		}
	}
	return FALSE;
}

function positionRecoverAll(&$Model, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField)) {
			return FALSE;
		}
	}
	$all = $Model->find('all', array('fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'order' => array($Model->escapeField($sortField) => 'ASC', $Model->escapeField($Model->primaryKey) => 'ASC'), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (is_array($all)) {
		$numRows = count($all);
		if ($numRows > 0) {
			foreach ($all as $i => $row) {
				$all[$i][$Model->alias][$sortField] = 100 + $i * 10;
			}
			return $Model->saveAll($all, array('validate' => FALSE, 'callbacks' => FALSE, 'atomic' => TRUE));
		}
	}
	return FALSE;
}

function positionReverse(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$all = $Model->find('all', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'order' => array($Model->escapeField($sortField) => 'DESC', $Model->escapeField($Model->primaryKey) => 'DESC'), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (is_array($all)) {
		$numRows = count($all);
		if ($numRows > 0) {
			foreach ($all as $i => $row) {
				$all[$i][$Model->alias][$sortField] = 100 + $i * 10;
			}
			return $Model->saveAll($all, array('validate' => FALSE, 'callbacks' => FALSE, 'atomic' => TRUE));
		}
	}
	return FALSE;
}

function positionReverseAll(&$Model, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField)) {
			return FALSE;
		}
	}
	$all = $Model->find('all', array('fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'order' => array($Model->escapeField($sortField) => 'DESC', $Model->escapeField($Model->primaryKey) => 'DESC'), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (is_array($all)) {
		$numRows = count($all);
		if ($numRows > 0) {
			foreach ($all as $i => $row) {
				$all[$i][$Model->alias][$sortField] = 100 + $i * 10;
			}
			return $Model->saveAll($all, array('validate' => FALSE, 'callbacks' => FALSE, 'atomic' => TRUE));
		}
	}
	return FALSE;
}

function getPosition(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return NULL;
		}
	}
	$data = $Model->find('first', array('conditions' => array($Model->escapeField($Model->primaryKey) => $id), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => FALSE));
	if (isset($data[$Model->alias][$sortField])) {
		return (int)$data[$Model->alias][$sortField];
	} else {
		return NULL;
	}
}

function setPosition(&$Model, $id, $value, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
	}
	return (bool)$Model->save(array($Model->alias => array($Model->primaryKey => $id, $sortField => (int)$value)), array('validate' => FALSE, 'callbacks' => FALSE));
}

function positionValue(&$Model, $id, $value = NULL, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return is_null($value) ? NULL : FALSE;
		}
	}
	if (is_null($value)) {
		return $this->getPosition($Model, $id, FALSE);
	} else {
		return $this->setPosition($Model, $id, $value, FALSE);
	}
}

function positionBetween(&$Model, $id, $neighborId1, $neighborId2, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id) || !$this->_idExists($Model, $neighborId1) || !$this->_idExists($Model, $neighborId2)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$data = $Model->find('all', array('conditions' => Set::merge($conditions, array($Model->escapeField($Model->primaryKey) => array($neighborId1, $neighborId2))), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (isset($data[0][$Model->alias][$sortField], $data[1][$Model->alias][$sortField])) {
		$space = abs($data[0][$Model->alias][$sortField] - $data[1][$Model->alias][$sortField]) - 1;
		if ($space > 0) {
			return $this->setPosition($Model, $id, min($data[0][$Model->alias][$sortField], $data[1][$Model->alias][$sortField]) + ceil($space / 2), FALSE);
		} elseif ($this->positionRecover($Model, $id, FALSE)) {
			$data = $Model->find('all', array('conditions' => Set::merge($conditions, array($Model->escapeField($Model->primaryKey) => array($neighborId1, $neighborId2))), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => $findCallbacks));
			if (isset($data[0][$Model->alias][$sortField], $data[1][$Model->alias][$sortField])) {
				$space = abs($data[0][$Model->alias][$sortField] - $data[1][$Model->alias][$sortField]) - 1;
				if ($space > 0) {
					return $this->setPosition($Model, $id, min($data[0][$Model->alias][$sortField], $data[1][$Model->alias][$sortField]) + ceil($space / 2), FALSE);
				}
			}
		}
	}
	return FALSE;
}

function positionBefore(&$Model, $id, $neighborId, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id) || !$this->_idExists($Model, $neighborId)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$data = $Model->find('first', array('conditions' => Set::merge($conditions, array($Model->escapeField($Model->primaryKey) => $neighborId)), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (isset($data[$Model->alias][$sortField])) {
		$neighbors = $Model->find('neighbors', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'field' => $sortField, 'value' => $data[$Model->alias][$sortField], 'recursive' => -1, 'callbacks' => $findCallbacks));
		if (isset($neighbors['next'][$Model->alias][$Model->primaryKey])) {
			return $this->positionBetween($Model, $id, $neighborId, $neighbors['next'][$Model->alias][$Model->primaryKey], FALSE);
		} else {
			return $this->setPosition($Model, $id, $data[$Model->alias][$sortField] + 10, FALSE);
		}
	}
	return FALSE;
}

function positionAfter(&$Model, $id, $neighborId, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id) || !$this->_idExists($Model, $neighborId)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$data = $Model->find('first', array('conditions' => Set::merge($conditions, array($Model->escapeField($Model->primaryKey) => $neighborId)), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (isset($data[$Model->alias][$sortField])) {
		$neighbors = $Model->find('neighbors', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'field' => $sortField, 'value' => $data[$Model->alias][$sortField], 'recursive' => -1, 'callbacks' => $findCallbacks));
		if (isset($neighbors['prev'][$Model->alias][$Model->primaryKey])) {
			return $this->positionBetween($Model, $id, $neighborId, $neighbors['prev'][$Model->alias][$Model->primaryKey], FALSE);
		} else {
			//return $this->setPosition($Model, $id, $data[$Model->alias][$sortField] - 10, FALSE);
			$space = $data[$Model->alias][$sortField] - 1;
			if ($space > 0) {
				return $this->setPosition($Model, $id, ceil($space / 2), FALSE);
			} elseif ($this->positionRecover($Model, $id, FALSE)) {
				$data = $Model->find('first', array('conditions' => Set::merge($conditions, array($Model->escapeField($Model->primaryKey) => $neighborId)), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => $findCallbacks));
				if (isset($data[$Model->alias][$sortField])) {
					$space = $data[$Model->alias][$sortField] - 1;
					if ($space > 0) {
						return $this->setPosition($Model, $id, ceil($space / 2), FALSE);
					}
				}
			}
		}
	}
	return FALSE;
}

function positionUp(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$data = $Model->find('first', array('conditions' => array($Model->escapeField($Model->primaryKey) => $id), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (isset($data[$Model->alias][$sortField])) {
		$neighbors = $Model->find('neighbors', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'field' => $sortField, 'value' => $data[$Model->alias][$sortField], 'recursive' => -1, 'callbacks' => $findCallbacks));
		if (isset($neighbors['next'][$Model->alias][$Model->primaryKey])) {
			return $this->positionBefore($Model, $id, $neighbors['next'][$Model->alias][$Model->primaryKey], FALSE);
		}
	}
	return FALSE;
}

function positionDown(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$data = $Model->find('first', array('conditions' => array($Model->escapeField($Model->primaryKey) => $id), 'fields' => array($Model->escapeField($sortField)), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (isset($data[$Model->alias][$sortField])) {
		$neighbors = $Model->find('neighbors', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey), $Model->escapeField($sortField)), 'field' => $sortField, 'value' => $data[$Model->alias][$sortField], 'recursive' => -1, 'callbacks' => $findCallbacks));
		if (isset($neighbors['prev'][$Model->alias][$Model->primaryKey])) {
			return $this->positionAfter($Model, $id, $neighbors['prev'][$Model->alias][$Model->primaryKey], FALSE);
		}
	}
	return FALSE;
}

function positionFirst(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$first = $Model->find('first', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey)), 'order' => array($Model->escapeField($sortField) => 'DESC'), 'recursive' => -1, 'callbacks' => $findCallbacks));
	if (isset($first[$Model->alias][$Model->primaryKey]) && ($first[$Model->alias][$Model->primaryKey] != $id)) {
		return $this->positionBefore($Model, $id, $first[$Model->alias][$Model->primaryKey], FALSE);
	}
	return FALSE;
}

function positionLast(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if (($conditions = $this->_conditions($Model, $id)) === FALSE) {
		return FALSE;
	}
	$numRows = $Model->find('count', array('conditions' => $conditions, 'recursive' => -1, 'callbacks' => $findCallbacks));
	if ($numRows > 0) {
		$last = $Model->find('first', array('conditions' => $conditions, 'fields' => array($Model->escapeField($Model->primaryKey)), 'order' => array($Model->escapeField($sortField) => 'DESC'), 'offset' => $numRows - 1, 'recursive' => -1, 'callbacks' => $findCallbacks));
		if (isset($last[$Model->alias][$Model->primaryKey]) && ($last[$Model->alias][$Model->primaryKey] != $id)) {
			return $this->positionAfter($Model, $id, $last[$Model->alias][$Model->primaryKey], FALSE);
		}
	}
	return FALSE;
}

function positionNew(&$Model, $id, $validate = TRUE) {
	extract($this->_getSettings($Model), EXTR_SKIP);
	if ($validate) {
		if (!$Model->hasField($sortField) || !$this->_idExists($Model, $id)) {
			return FALSE;
		}
		if (!$this->positionVerify($Model, $id, FALSE) && !$this->positionRecover($Model, $id, FALSE)) {
			return FALSE;
		}
	}
	if ($newPositionLast) {
		return $this->positionLast($Model, $id, FALSE);
	} else {
		return $this->positionFirst($Model, $id, FALSE);
	}
}

function afterSave(&$Model, $created) {
	if ($created) {
		$buffer = array($Model->id, $Model->data);
		$this->positionNew($Model, $Model->id);
		list($Model->id, $Model->data) = $buffer;
	}
}

}


?>